/*
 * Sales Data Controller Project.
 * Copyright (C) 2011 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.sdc.vsc.applet;

import org.globalplatform.GPSystem;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;
import javacard.security.KeyPair;
import javacard.security.RSAPublicKey;

/**
 * VAT Singing Card JavaCard applet.
 * 
 * @author Frank Cornelis
 * 
 */
public class VATSigningCardApplet extends Applet {

	/**
	 * ISO/IEC 7816-8: GENERATE PUBLIC KEY PAIR
	 */
	private final static byte INS_GENERATE_PUBLIC_KEY_PAIR = (byte) 0x46;

	private final static byte CLA_2 = (byte) 0x80;

	/**
	 * See: Belgian eID Card spec.
	 */
	private final static byte INS_GET_KEY = (byte) 0xE2;

	private final static byte INS_PUT_DATA = (byte) 0xDA;

	private final static short SW_WRONG_LENGTH_00 = (short) 0x6C00;

	private final static short SW_DATA_NOT_FOUND = (short) 0x6A88;

	private final static short TAG_PERSONALIZATION = (short) 0x51;

	KeyPair keyPair;

	byte[] vscNumber;

	/**
	 * Main constructor.
	 */
	private VATSigningCardApplet() {
		/*
		 * Register this applet instance with the Java Card runtime environment.
		 */
		register();
	}

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		new VATSigningCardApplet();
	}

	public void process(APDU apdu) throws ISOException {
		if (selectingApplet()) {
			/*
			 * In case the APDU is about selecting this applet instance, we
			 * simply skip the processing.
			 */
			return;
		}
		byte[] buffer = apdu.getBuffer();
		if (buffer[ISO7816.OFFSET_CLA] == ISO7816.CLA_ISO7816) {
			switch (buffer[ISO7816.OFFSET_INS]) {
			case INS_GENERATE_PUBLIC_KEY_PAIR:
				generateKeyPair(apdu);
				break;
			case INS_PUT_DATA:
				putData(apdu);
				break;
			default:
				ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
				break;
			}
		} else if (buffer[ISO7816.OFFSET_CLA] == CLA_2) {
			switch (buffer[ISO7816.OFFSET_INS]) {
			case INS_GET_KEY:
				getPublicKey(apdu);
				break;
			default:
				ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
				break;
			}
		} else {
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		}
	}

	/**
	 * ISO 7816-4 PUT DATA. Used to personalize.
	 * 
	 * TODO: command chaining in case personalization data object is > 255.
	 * 
	 * @param apdu
	 */
	private void putData(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		if (buffer[ISO7816.OFFSET_P1] != 0) {
			ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
		}

		short tag = (short) (buffer[ISO7816.OFFSET_P2] & (short) 0x00FF);
		if ((tag < (short) 0x0040) || (tag > (short) 0x00FE)) {
			ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
		}

		short lc = (short) (buffer[ISO7816.OFFSET_LC] & (short) 0x00FF);
		if (lc != apdu.setIncomingAndReceive()) {
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		}

		switch (tag) {
		case TAG_PERSONALIZATION:
			personalize(buffer);
			break;
		default:
			ISOException.throwIt(SW_DATA_NOT_FOUND);
		}
	}

	private void personalize(byte[] buffer) {
		short index = (short) ((ISO7816.OFFSET_CDATA) & 0x00FF);
		while (index < buffer.length) {
			byte tag = buffer[index];
			index++;
			if (0 == tag) {
				break;
			}
			short length = buffer[index];
			index++;
			if (0x08 == tag) {
				// VSC number
				if (8 != length) {
					ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
				}
				this.vscNumber = new byte[8];
				Util.arrayCopy(buffer, index, this.vscNumber, (short) 0,
						(short) 8);
			}
			index += length;
		}
	}

	/**
	 * Gives back the public key. Similar to BELPIC.
	 * 
	 * @param apdu
	 */
	private void getPublicKey(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) {
			ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
		}

		short le = apdu.setOutgoing();
		if (le != (short) (5 + 8 + 128)) {
			ISOException.throwIt((short) (SW_WRONG_LENGTH_00 + (5 + 8 + 128)));
		}

		byte[] tempTLVBuffer = new byte[le];
		tempTLVBuffer[(short) 0] = (byte) 0x02; // tag
		tempTLVBuffer[(short) 1] = (byte) 0x08; // length

		tempTLVBuffer[(short) 10] = (byte) 0x03; // tag
		tempTLVBuffer[(short) 11] = (byte) 0x81; // length
		tempTLVBuffer[(short) 12] = (byte) 0x80;

		((RSAPublicKey) this.keyPair.getPublic()).getExponent(tempTLVBuffer,
				(short) (2 + (8 - 3))); // LSB of exp on the right side
		((RSAPublicKey) this.keyPair.getPublic()).getModulus(tempTLVBuffer,
				(short) 13);

		apdu.setOutgoingLength(le);
		apdu.sendBytesLong(tempTLVBuffer, (short) 0, le);
	}

	private void generateKeyPair(APDU apdu) {
		if (GPSystem.getCardContentState() != GPSystem.APPLICATION_SELECTABLE) {
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
		}
		this.keyPair = new KeyPair(KeyPair.ALG_RSA_CRT, (short) (1024));
		this.keyPair.genKeyPair();
	}
}
